package p2pcdnsim.network;

import p2pcdnsim.report.SimReporter;
import desmoj.core.simulator.Model;
import desmoj.core.simulator.SimTime;

public class Link  {
	
	//The number of overhead bits due to frame creation on link layer
	private static final int LINKLAYER_OVERHEAD = 64;

	protected String id;
	
	//The nodes this link is supposed to connect
	protected Node from,to; 
	
	//The capacity of this link
	protected long capacity;
	
	//The processing delay of this link
	protected SimTime delay;
	
	//The queue associated to this link
	protected Queue queue;
	
	//An accumulator to the total bits sent over this link;
	protected int totalBitSent;
	
	private double lastTime;
	
	Model model;
	
	
	public Link(Model model, Node n1, Node n2, long capacity, SimTime delay, Queue queue) {
		this.model = model;
		this.id = "Link"+n1.getNodeId()+"-"+n2.getNodeId();
		this.from = n1;
		this.to = n2;
		this.capacity = capacity;
		this.delay = delay;
		this.queue = queue;
		this.totalBitSent = 0;	
		this.lastTime = 0;
	}
	
	public Link(Model model, Node n1, Node n2, int capacity, SimTime delay) {
		this(model, n1, n2,capacity, delay, new DropTail());
	}
	
	public Node getNodeFrom() {
		return from;
	}

	public Node getNodeTo() {
		return to;
	}

	public String getLinkId() {
		return id;
	}
	
	protected void deliverPacket(Packet p) {
		this.to.receivePacket(p);
	}

	@Override
	public boolean equals(Object obj) {
		return id.equals(((Link) obj).id);
	}
	
	/**
	 * Schedules a packet to be transmitted over this link
	 * @param p - the packet to be transmitted
	 */
	public void sendPacket(Packet p) {
		addPacketOnQueue(p);
	}
	
	private void addPacketOnQueue(Packet packet) {
		long bitsToSend = getBitsToSend(packet);
		if(lastTime < (model.currentTime().getTimeValue()+delay.getTimeValue()))
			lastTime = model.currentTime().getTimeValue()+delay.getTimeValue();
		lastTime += (((double)bitsToSend)/capacity);
		if (!queue.addPacket(packet)) {
			SimReporter.getInstance().invokeReporter(SimReporter.PACKET_LOSS,"update");
		} else {
			LinkControl.getInstance().schedulePacket(packet,this,lastTime);
		}
	}

	public int getBitsSent() {
		return totalBitSent;
	}

	void processPacket(Packet packet) {
		if(!queue.getNextPacket().equals(packet)) throw new RuntimeException("Unexpected packet!");
		
		long bitsToSend = getBitsToSend(packet);
		this.totalBitSent += bitsToSend;
		this.deliverPacket(packet);
		
		//updates the bandwidth consumption
		SimReporter.getInstance().invokeReporter(SimReporter.BANDWIDTH_CONSUMPTION, "update", getLinkId(), bitsToSend);
	}
	
	private long getBitsToSend(Packet packet) {
		return LINKLAYER_OVERHEAD + packet.getSize() * 8;
	}
	
//-----------------------------------------------------------------------------------------
	
//	public Link(Model owner, Node n1, Node n2, long capacity, SimTime delay, Queue queue) {
//		super(owner, "Link "+n1.getNodeId()+"-"+n2.getNodeId(), true);
//		this.id = "Link"+n1.getNodeId()+"-"+n2.getNodeId();
//		this.from = n1;
//		this.to = n2;
//		this.capacity = capacity;
//		this.delay = delay;
//		this.queue = queue;
//		this.totalBitSent = 0;
//	}
//
//	public Link(Model owner, Node n1, Node n2, int capacity, SimTime delay) {
//		this(owner, n1, n2,capacity, delay, new DropTail());
//	}
//	
//	public Node getNodeFrom() {
//		return from;
//	}
//
//	public Node getNodeTo() {
//		return to;
//	}
//
//	public String getLinkId() {
//		return id;
//	}
//
//	@Override
//	public void lifeCycle() {
//		while(true) {
//			Packet p = queue.getNextPacket();
//			if(p == null) {
//				//System.out.println("Passivating...");
//				passivate();
//				//System.out.println("Waking up");
//			} else {
//				//Calculated the amount of bits to be sent
//				//Based on this, the necessary time to be transmitted is
//				//calculated
//				long bitsToSend = LINKLAYER_OVERHEAD + p.getSize() * 8;		
//				//System.out.println("Node "+this.from.getNodeId()+ ": sending packet "+p.getId());
//				//Hold the time necessary to send this amount of bits
//				if(this.isScheduled()) throw new RuntimeException("trying to hold but already scheduled at "+this.scheduledAt());
//				this.hold(new SimTime(((double)bitsToSend)/capacity));
//				this.totalBitSent += bitsToSend;
//				this.deliverPacket(p);
//				
//				//updates the bandwidth consumption
//				SimReporter.getInstance().invokeReporter(SimReporter.BANDWIDTH_CONSUMPTION, "update", getLinkId(), bitsToSend);
//			}
//		}
//	}
//
//
//	protected void deliverPacket(Packet p) {
//		this.to.receivePacket(p);
//	}
//
//	@Override
//	public boolean equals(Object obj) {
//		return id.equals(((Link)obj).id);
//	}
//
//	/**
//	 * Schedules a packet to be transmitted over this link
//	 * @param p - the packet to be transmitted
//	 */
//	public void sendPacket(Packet p) {
//		
//		if(delay.getTimeValue() == 0) {
//			addPacketOnQueue(p);
//		} else {
//		//Delay the insertion of packet due to processing time
//		Delayer d = new Delayer(this,delay,p);
//		d.activate(new SimTime(0.0));
//		}
//	}
//
//	private void addPacketOnQueue(Packet packet) {
//		if(!queue.addPacket(packet)) {
//			//System.out.println("Packet dropped");
//			//sentTraceNote()
//			SimReporter.getInstance().invokeReporter(SimReporter.PACKET_LOSS, "update");
//		}
//		if(!this.isScheduled()) this.activate(new SimTime(0.0));
//	}
//	
//	public int getBitsSent() {
//		return totalBitSent;
//	}
//	
//	//This class is used to create a delay to the packet be inserted
//	//into the queue
//	private class Delayer extends SimProcess {
//		
//		Link link;
//		SimTime delay;
//		Packet packet;
//		
//		/**
//		 * Creates a Delayer
//		 * @param link - the corresponding link
//		 * @param delay - the desired delay
//		 * @param p - the packet to be delayed
//		 */
//		public Delayer(Link link, SimTime delay, Packet p) {
//			//Do not show this in trace otherwise too much information is generated
//			super(link.model, "packet delayer", false);
//			this.link =link;
//			this.delay = delay;
//			this.packet = p;
//		}
//		
//		public void lifeCycle() {
//			//Actually delay the packet
//			if(delay.getTimeValue() != 0) hold(delay);
//			//Add the packet to the queue
//			link.addPacketOnQueue(packet);
//		}
//	}
}
